const PROMISE_STATUS_PENDING = "pending";
const PROMISE_STATUS_FULFILLED = "fulfilled";
const PROMISE_STATUS_REJECTED = "rejected";

const execFunctionWithCatchError = (excuFn, value, resolve, reject) => {
  try {
    const result = excuFn(value);
    resolve(result);
  } catch (error) {
    reject(error);
  }
};

class MyPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.fulfilledFns = [];
    this.rejectedFns = [];
    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.fulfilledFns.forEach((fn) => {
            fn();
          });
        });
      }
    };

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.rejectedFns.forEach((fn) => {
            fn();
          });
        });
      }
    };
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(fulfilled, rejected) {
    return new MyPromise((resolve, reject) => {
      const defaultOnRejected = (err) => {
        throw err;
      };
      rejected = rejected || defaultOnRejected;

      const defaultOnFulfilled = (value) => {
        return value;
      };
      fulfilled = fulfilled || defaultOnFulfilled;

      if (this.status === PROMISE_STATUS_FULFILLED && fulfilled) {
        execFunctionWithCatchError(fulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && rejected) {
        execFunctionWithCatchError(rejected, this.reason, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_PENDING) {
        if (fulfilled)
          this.fulfilledFns.push(() => {
            execFunctionWithCatchError(fulfilled, this.value, resolve, reject);
          });
        if (rejected)
          this.rejectedFns.push(() => {
            execFunctionWithCatchError(rejected, this.reason, resolve, reject);
          });
      }
    });
  }

  catch(rejected) {
    return this.then(undefined, rejected);
  }

  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }

  static resolve(value) {
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      const values = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res);
            console.log(values, promises.length);
            if (values.length === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
            if (results.length === promises.length) {
              resolve(results);
            }
          },
          (err) => {
            results.push({ status: PROMISE_STATUS_REJECTED, value: err });
            if (results.length === promises.length) {
              resolve(results);
            }
          }
        );
      });
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, reject);
      });
    });
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const results = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            results.push(err);
            if (results.length === promises.length) {
              reject(results);
            }
          }
        );
      });
    });
  }
}

// MyPromise.resolve("Hello World").then((res) => {
//   console.log("res:", res);
// });

// MyPromise.reject("Error Message").catch((err) => {
//   console.log("err:", err);
// });

// const p1 = new Promise((resolve) => {
//   setTimeout(() => {
//     resolve(1111);
//   }, 1000);
// });
// const p2 = new Promise((resolve, reject) => {
//   setTimeout(() => {
//     reject(2222);
//   }, 2000);
// });
// const p3 = new Promise((resolve) => {
//   setTimeout(() => {
//     resolve(3333);
//   }, 3000);
// });
// MyPromise.all([p1, p2, p3])
//   .then((res) => {
//     console.log(res);
//   })
//   .catch((err) => {
//     console.log(err);
//   });

// MyPromise.allSettled([p1, p2, p3]).then((res) => {
//   console.log(res);
// });

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(1111);
  }, 3000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2222);
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3333);
  }, 3000);
});

MyPromise.race([p1, p2, p3])
  .then((res) => {
    console.log("res:", res);
  })
  .catch((err) => {
    console.log("err:", err);
  });

// MyPromise.any([p1, p2, p3]).then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err.errors)
// })
